package com.mandao.oneplat.boot.util;

import com.mandao.oneplat.boot.ioc.OneIocFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * Created by sunson on 2017/3/29.
 */
public final class PackageUtil {

    private static ConcurrentHashMap<String, List<Class>> scanResultMap = new ConcurrentHashMap<>();


    public static List<Class> scanPackages(String pkges) throws IOException {
        return scanPackages(pkges, true);
    }


    public static List<Class> scanPackages(String pkges, boolean useCache) throws IOException {
        List<Class> classList = new ArrayList<>();
        for (String pkg : pkges.split(";")) {
            classList.addAll(scanPackage(pkg, useCache));
        }
        return classList;
    }

    public static List<Class> scanPackage(String pkg, boolean useCache) throws IOException {
        if (useCache && scanResultMap.containsKey(pkg)) {
            return scanResultMap.get(pkg);
        }
        List<String> nameList = new ArrayList<>();
        doSubScanPackage(pkg, nameList);
        List<Class> classList = new ArrayList<>();

        for (String clsname : nameList) {
            try {
                classList.add(Class.forName(clsname));
            } catch (ClassNotFoundException e) {
            }
        }
        if (useCache) {
            scanResultMap.put(pkg, classList);
        }
        return classList;
    }

    static void doSubScanPackage(String pkg, List<String> nameList) throws IOException {
        String splashPath = dotToSplash(pkg);
        URL url = OneIocFactory.class.getClassLoader().getResource(splashPath);
        String filePath = getRootPath(url);
        List<String> names;
        if (isJarFile(filePath)) {
            names = readFromJarFile(filePath, splashPath);
        } else {
            names = readFromDirectory(filePath);
        }

        for (String name : names) {
            if (isClassFile(name)) {
                nameList.add(toFullyQualifiedName(name, pkg));
            } else {
                doSubScanPackage(pkg + "." + name, nameList);
            }
        }
    }


    static String toFullyQualifiedName(String shortName, String basePackage) {
        StringBuilder sb = new StringBuilder(basePackage);
        sb.append('.');
        sb.append(trimExtension(shortName));
        return sb.toString();
    }

    static List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException {
        JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
        JarEntry entry = jarIn.getNextJarEntry();

        List<String> nameList = new ArrayList<>();
        while (null != entry) {
            String name = entry.getName();
            if (name.startsWith(splashedPackageName) && isClassFile(name)) {
                nameList.add(trimURI(name));
            }
            entry = jarIn.getNextJarEntry();
        }
        return nameList;
    }

    static List<String> readFromDirectory(String path) {
        File file = new File(path);
        String[] names = file.list();

        if (null == names) {
            return null;
        }
        return Arrays.asList(names);
    }

    static boolean isClassFile(String name) {
        return name.endsWith(".class");
    }

    static boolean isJarFile(String name) {
        return name.endsWith(".jar");
    }

    static String getRootPath(URL url) {
        String fileUrl = url.getFile();
        int pos = fileUrl.indexOf('!');
        if (-1 == pos) {
            return fileUrl;
        }
        return fileUrl.substring(5, pos);
    }

    /**
     * "com.xc.oneplat" -> "com/xc/oneplat"
     *
     * @param name
     * @return
     */
    static String dotToSplash(String name) {
        return name.replaceAll("\\.", "/");
    }

    /**
     * "Apple.class" -> "Apple"
     */
    static String trimExtension(String name) {
        int pos = name.indexOf('.');
        if (-1 != pos) {
            return name.substring(0, pos);
        }
        return name;
    }

    /**
     * /application/home -> /home
     *
     * @param uri
     * @return
     */
    static String trimURI(String uri) {
        int splashIndex = uri.lastIndexOf('/');
        return uri.substring(splashIndex + 1);
    }

}
